package org.foreningsgatan.mercenaries.manual.shared;

import java.awt.event.KeyEvent;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.foreningsgatan.mercenaries.Configuration;
import org.foreningsgatan.mercenaries.helpers.terrain.TerrainMapGenerator;
import org.foreningsgatan.mercenaries.level.LevelLoader;
import org.foreningsgatan.mercenaries.model.LightSource;
import org.foreningsgatan.mercenaries.model.terrain.TerrainMap;
import org.foreningsgatan.mercenaries.rendering.LoggingUtils;
import org.foreningsgatan.mercenaries.rendering.Orientation;
import org.foreningsgatan.mercenaries.rendering.RenderUtils;
import org.foreningsgatan.mercenaries.rendering.Renderer;
import org.foreningsgatan.mercenaries.rendering.ScrollPosition;
import org.foreningsgatan.mercenaries.rendering.TerrainRenderer;
import org.foreningsgatan.mercenaries.rendering.TileClippingRect;
import org.foreningsgatan.modular.common.logging.Log;
import org.foreningsgatan.modular.common.performance.Timer;
import org.foreningsgatan.modular.lightsources.LightMap;
import org.foreningsgatan.modular.lightsources.LightSources;
import org.foreningsgatan.modular.lightsources.RGB;
import org.foreningsgatan.modular.resources.ResourceManager;
import org.foreningsgatan.modular.terrain.model.SimpleMap;
import org.foreningsgatan.modules.glcore.frames.SingleFrame;
import org.foreningsgatan.modules.glcore.graphics.Alignment;
import org.foreningsgatan.modules.glcore.graphics.BlendMode;
import org.foreningsgatan.modules.glcore.graphics.GLGraphics;
import org.foreningsgatan.modules.glcore.graphics.PackedTexture;
import org.foreningsgatan.modules.glcore.graphics.TextureManager;
import org.foreningsgatan.modules.glcore.view.GLApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 
 * Copyright (c) 2008 Joakim Back
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * @author Joakim Back
 * 
 */
public abstract class AbstractTerrainApplication extends GLApplication {
	static private final int sSCROLL_SPEED = 20;

	// static private final RGB sAMBIENT_LIGHT = new RGB(0.0f, 0.0f, 0.25f); //
	// Dark

	// static private final RGB sAMBIENT_LIGHT = new RGB(0.0f, 0.1f, 0.5f); //
	// Night

	// static private final RGB sAMBIENT_LIGHT = new RGB(0.8f, 0.4f, 0.2f); //
	// Evening

	static private final RGB cAmbientLight = new RGB(0.3f, 0.6f, 1.0f); //
	// Morning

	protected Log mLog = new Log(this.getClass());

	protected boolean mHasScrolled = true;
	
	private Map<Object, Long> mTextures = new HashMap<Object, Long>();

	protected LightSources<LightSource> mLightSources;

	protected SimpleMap mSimpleMap;

	protected TerrainMap mTerrainMap;

	protected LightMap mLightMap;

	protected SingleFrame mMarkerFrame;

	protected ScrollPosition mScrollPosition;

	protected TileClippingRect mClippingRect;

	private int mDebugCounter = 0;
	
	private ResourceManager mResourceManager;

	protected LevelLoader mLevelLoader;

	// Renderers

	List<Renderer> mRenderers;

	TerrainRenderer mTerrainRenderer;

	public AbstractTerrainApplication(String title, int width, int height,
			int fps) {
		super(title, width, height, fps);
	}

	@Override
	protected void init() throws Exception {
		ApplicationContext context = new ClassPathXmlApplicationContext("test-application-context.xml");
		
		mResourceManager = (ResourceManager) context.getBean("resourceManager");
		
		mLevelLoader = (LevelLoader) context.getBean("levelLoader");

		mSimpleMap = mLevelLoader.loadTerrain();

		mTerrainMap = TerrainMapGenerator.generate(mSimpleMap, mTextures);

		// Prepare LightSources

		mLightSources = new LightSources<LightSource>();

		mLightSources.setAmbientLight(cAmbientLight);

		for (LightSource vLightSource : mLevelLoader.loadLightSources()) {
			mLightSources.register(vLightSource);
		}

		mLightSources.updateStaticLights(Configuration.sMapSize + 2);

		// Prepare Scroll

		mScrollPosition = new ScrollPosition(0, 0);

		// Prepare Renderers

		mTerrainRenderer = new TerrainRenderer();

		mRenderers = new ArrayList<Renderer>();

		mRenderers.add(mTerrainRenderer);
	}

	@Override
	protected void loadTextures(TextureManager tm) {
		try {
			/* Initialize Resource Manager */
			mResourceManager.addHandler(new TextureResourceHandler(tm), "png");

			/* Load Textures */

			TerrainTexturesLoader.load(mTextures, tm);
			VegetationTexturesLoader.load(tm);

			URL vURL = Thread.currentThread().getContextClassLoader()
					.getResource("./textures/Marker.png");
			File vFile = new File(vURL.getFile().replaceAll("%20", " "));

			PackedTexture markerTexture = tm.getPackedTextureLoader()
					.add(vFile);

			tm.packTextures();

			mMarkerFrame = new SingleFrame(markerTexture);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void processKeys() {
		int dX = 0;
		int dY = 0;

		if (isKeyPressed(KeyEvent.VK_LEFT)) {
			dX = -sSCROLL_SPEED;
		}

		if (isKeyPressed(KeyEvent.VK_RIGHT)) {
			dX = sSCROLL_SPEED;
		}

		if (isKeyPressed(KeyEvent.VK_UP)) {
			dY = -sSCROLL_SPEED;
		}

		if (isKeyPressed(KeyEvent.VK_DOWN)) {
			dY = sSCROLL_SPEED;
		}

		if ((dX != 0) || (dY != 0)) {
			mScrollPosition.move(dX, dY);
			mHasScrolled = true;
		}
	}

	private void renderTerrain(GLGraphics g) throws Exception {
		/* Prepare offset */

		mClippingRect = TileClippingRect.getInstance(width, height,
				mScrollPosition.getX(), mScrollPosition.getY(),
				Configuration.sTileWidth, Configuration.sTileHeight);

		g.enableBlend();

		/* Update LightMap */

		Timer t = new Timer();

		mLightMap = mLightSources.render(mClippingRect.getVerticalHeight()
				+ Configuration.sFrameLightMapMargin, mClippingRect
				.getHorizontalFirst() - 1,
				mClippingRect.getVerticalFirst() - 1, true);

		if (LoggingUtils.isActive)
			mLog.debug("LightSources took %s", t);

		// Render Layers

		mTerrainRenderer.update(mTerrainMap, mLightMap);

		for (Renderer mRenderer : mRenderers) {
			t = new Timer();

			mRenderer.render(g, mClippingRect, mScrollPosition, mHasScrolled);

			if (LoggingUtils.isActive)
				mLog.debug("%s : %s", mRenderer.getClass().getSimpleName(), t
						.toString());
		}

		/* Draw Light Sources */

		// renderLightSourceMarkers(g);
		if (LoggingUtils.isActive)
			mLog.debug("Terrain took %s", t);
	}

	@SuppressWarnings("unused")
	private void renderLightSourceMarkers(GLGraphics pGraphics) {
		for (org.foreningsgatan.modular.lightsources.LightSource vLight : mLightSources
				.getLightSources(Configuration.sMapSize, 0, 0)) {
			float x = RenderUtils.toScreenCoord(vLight.getX(),
					Orientation.HORIZONTAL, mScrollPosition);
			float y = RenderUtils.toScreenCoord(vLight.getY(),
					Orientation.VERTICAL, mScrollPosition);

			pGraphics.drawFrames(x, y, mMarkerFrame, Alignment.CENTER, 1.0f,
					GLGraphics.WHITE, BlendMode.ALPHA);
		}
	}

	@Override
	protected void render(GLGraphics g) {
		/* Debugging */

		mDebugCounter = (mDebugCounter + 1) % 60;

		LoggingUtils.isActive = (mDebugCounter == 0);

		if (LoggingUtils.isActive)
			mLog.debug("----- Frame -----");

		processKeys();

		// Move Lightsources

		for (LightSource vLightSource : mLightSources.getLightSources(
				Configuration.sMapSize, 0, 0)) {
			vLightSource.setY(vLightSource.getY() + 0.05);

			if (vLightSource.getY() > Configuration.sMapSize) {
				vLightSource.setY(0);
			}
		}

		try {
			renderTerrain(g);
			// renderLightSourceMarkers(g);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected void drawNavigation(GLGraphics g, float offsetX, float offsetY) {
		g.reset();

		g.fillRect(offsetX, offsetY, mSimpleMap.getSize(),
				mSimpleMap.getSize(), new float[] { 1.0f, 1.0f, 1.0f, 0.25f });

		float mx = (float) mScrollPosition.getX() / Configuration.sTileWidth;
		float my = (float) mScrollPosition.getY() / Configuration.sTileHeight;
		float mw = (float) g.getWidth() / Configuration.sTileWidth;
		float mh = (float) g.getHeight() / Configuration.sTileHeight;

		g.fillRect(offsetX + mx, offsetY + my, mw, mh, new float[] { 1.0f,
				1.0f, 1.0f, 0.75f });
	}

	@Override
	protected void afterRender() {
		super.afterRender();

		mHasScrolled = false;
	}
}